Skip to content

AIFunctions

AIFunctions are single, focused operations that the agent can call. They are the simplest capability type and the building block for more complex capabilities.

Basic Usage

Mark a method with [AIFunction] to expose it to the agent:

csharp
public class CalculatorTool
{
    [AIFunction]
    public int Add(int a, int b)
    {
        return a + b;
    }
}

The agent sees this as a tool called Add that takes two integers and returns an integer.


Descriptions

Function Description

Use [AIDescription] to tell the agent what the function does:

csharp
[AIFunction]
[AIDescription("Add two numbers together and return the sum")]
public int Add(int a, int b)
{
    return a + b;
}

Parameter Descriptions

Apply [AIDescription] to parameters for clarity:

csharp
[AIFunction]
[AIDescription("Search the web for information")]
public async Task<string> WebSearch(
    [AIDescription("The search query")] string query,
    [AIDescription("Maximum number of results (1-10)")] int maxResults = 5)
{
    // Implementation
}

Return Types

AIFunctions support various return types:

csharp
// Synchronous
[AIFunction]
public string GetName() => "HPD-Agent";

// Async
[AIFunction]
public async Task<string> GetNameAsync() => await Task.FromResult("HPD-Agent");

// Complex types (serialized to JSON)
[AIFunction]
public async Task<WeatherResult> GetWeather(string city)
{
    return new WeatherResult { Temperature = 72, Condition = "Sunny" };
}

// Void (returns confirmation message)
[AIFunction]
public void LogMessage(string message)
{
    Console.WriteLine(message);
}

Requiring Permission

Some functions need user approval before execution. Use [RequiresPermission]:

csharp
[AIFunction]
[AIDescription("Delete a file from the filesystem")]
[RequiresPermission]
public void DeleteFile(string path)
{
    File.Delete(path);
}

When the agent calls this function, the user will be prompted to approve or deny the action.


Custom Function Names

Override the default method name:

csharp
[AIFunction(Name = "search_web")]
public async Task<string> PerformWebSearch(string query)
{
    // Implementation
}

The agent sees this as search_web, not PerformWebSearch.


The Toolkit Attribute

When a toolkit has multiple functions, you can group them under a collapsible container using [Toolkit("description")]:

csharp
[Toolkit("File operations for reading, writing, and managing files")]
public class FileSystemToolkit
{
    [AIFunction]
    public string ReadFile(string path) { /* ... */ }

    [AIFunction]
    public void WriteFile(string path, string content) { /* ... */ }

    [AIFunction]
    public void DeleteFile(string path) { /* ... */ }
}

Note: Providing a description to [Toolkit] enables collapsing. Without a description ([Toolkit] or [Toolkit(Name = "...")]), the toolkit's functions are always visible.

Toolkit with Instructions

The [Toolkit] attribute supports two instruction types that serve different purposes:

csharp
[Toolkit(
    "Database operations",
    FunctionResult: "Available: Query, Insert, Update, Delete. Always use transactions for multiple operations.",
    SystemPrompt: "CRITICAL: Never execute DELETE without a WHERE clause."
)]
public class DatabaseToolkit
{
    // Functions...
}
PropertyWhere It GoesWhenPersistence
FunctionResultTool call resultOnce on expansionIn message history
SystemPromptSystem instructionsEvery turn while expandedConfigurable

FunctionResult: One-time message returned when the container expands. Use for listing available functions or one-time guidance.

SystemPrompt: Injected into system instructions while functions are expanded. Use for critical rules that must be followed every turn.

Dynamic Instructions with Expressions

Reference methods or properties for dynamic instructions:

csharp
[Toolkit(
    "Search operations",
    FunctionResult: nameof(GetAvailableSearchers),
    SystemPrompt: nameof(SearchGuidelines)
)]
public class SearchToolkit
{
    public static string GetAvailableSearchers() =>
        $"Available: {string.Join(", ", _enabledSearchers)}";

    public static string SearchGuidelines =>
        "Always prefer exact matches over fuzzy matches.";

    // Functions...
}

→ See 02.1.5 Context Engineering.md for advanced collapsing configuration.


Dependency Injection

Tools can receive services through constructor injection:

csharp
public class WeatherTool
{
    private readonly IWeatherService _weatherService;
    private readonly ILogger<WeatherTool> _logger;

    public WeatherTool(IWeatherService weatherService, ILogger<WeatherTool> logger)
    {
        _weatherService = weatherService;
        _logger = logger;
    }

    [AIFunction]
    [AIDescription("Get current weather for a city")]
    public async Task<WeatherResult> GetWeather(string city)
    {
        _logger.LogInformation("Getting weather for {City}", city);
        return await _weatherService.GetCurrentWeatherAsync(city);
    }
}

Register the service provider with the agent:

csharp
var services = new ServiceCollection()
    .AddSingleton<IWeatherService, WeatherService>()
    .AddLogging()
    .BuildServiceProvider();

var agent = new AgentBuilder()
    .WithServiceProvider(services)
    .WithTool<WeatherTool>()
    .Build();

Typed Metadata

For compile-time validation of dynamic descriptions and conditionals, use the generic attribute:

csharp
public class SearchMetadata : IToolMetadata
{
    public string ProviderName { get; set; } = "Default";
    public bool HasAdvancedFeatures { get; set; } = false;
    // IToolMetadata implementation...
}

public class SearchTool
{
    [AIFunction<SearchMetadata>]
    [AIDescription("Search using {metadata.ProviderName}")]
    public async Task<string> Search(string query)
    {
        // Implementation
    }
}

→ See 02.1.4 Tool Metadata.md for details on metadata and dynamic descriptions.


Conditional Functions

Show or hide functions based on runtime conditions:

csharp
[AIFunction]
[ConditionalFunction("HasAdvancedFeatures")]
[AIDescription("Advanced search with filters")]
public async Task<string> AdvancedSearch(string query, SearchFilters filters)
{
    // Only visible when metadata.HasAdvancedFeatures is true
}

→ See 02.1.4 Tool Metadata.md for conditional registration details.


Best Practices

  1. Keep functions focused: One function should do one thing well.

  2. Write clear descriptions: The agent relies on descriptions to choose the right function.

  3. Use meaningful parameter names: city is better than c or input.

  4. Handle errors gracefully: Return error information rather than throwing exceptions when possible.

  5. Use [RequiresPermission] for destructive or sensitive operations.

  6. Group related functions: Use [Toolkit("description")] to reduce context clutter.

csharp
// Good: Focused, well-described, safe
[AIFunction]
[AIDescription("Search for files matching a pattern in a directory")]
public async Task<string[]> FindFiles(
    [AIDescription("Directory to search in")] string directory,
    [AIDescription("Glob pattern (e.g., '*.txt')")] string pattern)
{
    if (!Directory.Exists(directory))
        return Array.Empty<string>();

    return Directory.GetFiles(directory, pattern);
}

// Bad: Vague, no descriptions, unsafe
[AIFunction]
public void Process(string x)
{
    File.Delete(x);  // Destructive without permission!
}

Next Steps

Released under the MIT License.